package com.dylan.channel.v10.channel;

import com.dylan.channel.v10.config.DefaultSocketChannelConfig;
import com.dylan.channel.v10.config.SocketChannelConfig;
import com.dylan.channel.v10.handler.ChannelPipeline;
import com.dylan.channel.v10.option.ChannelOption;
import com.dylan.channel.v10.option.NioChannelOption;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Map;

public class NioSocketChannel extends AbstractNioByteChannel implements DuplexChannel {
    private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
    private final SocketChannelConfig config;

    public NioSocketChannel() {
        this(DEFAULT_SELECTOR_PROVIDER);
    }

    private static SocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openSocketChannel();
        } catch (IOException var2) {
            throw new RuntimeException("Failed to open a socket.", var2);
        }
    }

    public NioSocketChannel(SelectorProvider provider) {
        this(newSocket(provider));
    }

    public NioSocketChannel(SocketChannel socket) {
        this(null, socket);
    }

    public NioSocketChannel(Channel parent, SocketChannel socket) {
        super(parent, socket, SelectionKey.OP_READ);
        this.config = new NioSocketChannelConfig(this, socket.socket());
    }

    protected SocketChannel javaChannel() {
        SocketChannel channel = (SocketChannel) super.javaChannel();
        try {
            channel.configureBlocking(false);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return channel;
    }

    @Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        javaChannel().bind(localAddress);
    }


    protected boolean doConnect(SocketAddress remoteAddress) {
        //在这里把channel注册到单线程执行器中的selector上,注意这里的第三个参数this，这意味着channel注册的时候把本身，也就是nio类的channel
        //当作附件放到key上了，之后会用到这个。
        try {

            SocketChannel channel = javaChannel();
            //从这里可以看出，如果连接的时候把本地地址也传入了，那么就要在连接远端的同时，监听本地端口号
            doBind(new InetSocketAddress(8087));

            boolean connected = channel.connect(remoteAddress);
            if (!connected) {
                selectionKey.interestOps(SelectionKey.OP_CONNECT);
            }
            return connected;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    @Override
    protected int doReadBytes(ByteBuffer byteBuf) throws Exception {
        SocketChannel channel = javaChannel();
        int len = channel.read(byteBuf);

        //首先得到客户端channel的ChannelPipeline链表
        ChannelPipeline p = this.pipeline();
        //开始流水化处理消息
        p.fireChannelRead(byteBuf);
        //返回读取到的字节长度
        return len;
    }
    @Override
    protected void doWrite(Object msg) throws Exception {
        //真正发送数据的时候到了，这时候就不能用NioSocketChannel了，要用java原生的socketchannel
        SocketChannel socketChannel = javaChannel();
        //转换数据类型
        ByteBuffer buffer = (ByteBuffer)msg;
        //发送数据
        socketChannel.write(buffer);
        //因为在我们自己的netty中，客户端的channel连接到服务端后，并没有绑定单线程执行器呢，所以即便发送了数据也收不到
        //但我们可以看看客户端是否可以发送成功，证明我们的发送逻辑是没问题的，接收数据的验证，让我们放到引入channelhandler之后再验证
        System.out.println("发送数据成功了！");
    }
    @Override
    public SocketChannelConfig config() {
        return config;
    }

    /**
     * @Author: PP-jessica
     * @Description:用户设置的客户端channel的参数由此类进行设置，这里面有的方法现在还不需要是用来干什么的，等学完ByteBuf了会全部讲解
     */
    private final class NioSocketChannelConfig extends DefaultSocketChannelConfig {
        private volatile int maxBytesPerGatheringWrite = Integer.MAX_VALUE;

        private NioSocketChannelConfig(NioSocketChannel channel, Socket javaSocket) {
            super(channel, javaSocket);
            calculateMaxBytesPerGatheringWrite();
        }

        @Override
        public NioSocketChannelConfig setSendBufferSize(int sendBufferSize) {
            super.setSendBufferSize(sendBufferSize);
            calculateMaxBytesPerGatheringWrite();
            return this;
        }

        @Override
        public <T> boolean setOption(ChannelOption<T> option, T value) {
            if (option instanceof NioChannelOption) {
                return NioChannelOption.setOption(jdkChannel(), (NioChannelOption<T>) option, value);
            }
            return super.setOption(option, value);
        }

        @Override
        public <T> T getOption(ChannelOption<T> option) {
            if (option instanceof NioChannelOption) {
                return NioChannelOption.getOption(jdkChannel(), (NioChannelOption<T>) option);
            }
            return super.getOption(option);
        }

        @SuppressWarnings("unchecked")
        @Override
        public Map<ChannelOption<?>, Object> getOptions() {
            return getOptions(super.getOptions(), NioChannelOption.getOptions(jdkChannel()));
        }

        void setMaxBytesPerGatheringWrite(int maxBytesPerGatheringWrite) {
            this.maxBytesPerGatheringWrite = maxBytesPerGatheringWrite;
        }

        int getMaxBytesPerGatheringWrite() {
            return maxBytesPerGatheringWrite;
        }

        private void calculateMaxBytesPerGatheringWrite() {
            int newSendBufferSize = getSendBufferSize() << 1;
            if (newSendBufferSize > 0) {
                setMaxBytesPerGatheringWrite(getSendBufferSize() << 1);
            }
        }

        private SocketChannel jdkChannel() {
            return ((NioSocketChannel) channel).javaChannel();
        }
    }
}
